//-*****************************************************************************
//
// Copyright (c) 2009-2011,
//  Sony Pictures Imageworks Inc. and
//  Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Sony Pictures Imageworks, nor
// Industrial Light & Magic, nor the names of their contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//-*****************************************************************************

#include "WriteGeo.h"
#include "ArbGeomParams.h"

#include <ai.h>
#include <sstream>

#include <boost/regex.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>

//-*****************************************************************************

#if AI_VERSION_ARCH_NUM == 3
    #if AI_VERSION_MAJOR_NUM < 4
        #define AiNodeGetNodeEntry(node)   ((node)->base_node)
    #endif
#endif

bool nodeHasParameter( struct AtNode * node, const std::string & paramName)
{
    return AiNodeEntryLookUpParameter( AiNodeGetNodeEntry( node ),
            paramName.c_str() ) != NULL;
}

const std::string trueString("True");

//-*****************************************************************************

AtByte gaDetermineAttributeType(const std::string& attributeDeclaration)
{
	AiMsgDebug("gaDetermineAttributeType() attributeDeclaration = %s",
			attributeDeclaration.c_str());

    // Comparison order is important because find will return earlier on substring matches
    if (attributeDeclaration.find("BYTE") != std::string::npos)
        return AI_TYPE_BYTE;
    else if (attributeDeclaration.find("POINTER") != std::string::npos)
        return AI_TYPE_POINTER;
    else if (attributeDeclaration.find("POINT2") != std::string::npos)
        return AI_TYPE_POINT2;
    else if (attributeDeclaration.find("POINT") != std::string::npos)
        return AI_TYPE_POINT;
    else if (attributeDeclaration.find("UINT") != std::string::npos)
        return AI_TYPE_UINT;
    else if (attributeDeclaration.find("INT") != std::string::npos)
        return AI_TYPE_INT;
    else if (attributeDeclaration.find("BOOL") != std::string::npos)
        return AI_TYPE_BOOLEAN;
    else if (attributeDeclaration.find("FLOAT") != std::string::npos)
        return AI_TYPE_FLOAT;
    else if (attributeDeclaration.find("RGBA") != std::string::npos)
        return AI_TYPE_RGBA;
    else if (attributeDeclaration.find("RGB") != std::string::npos)
        return AI_TYPE_RGB;
    else if (attributeDeclaration.find("VECTOR") != std::string::npos)
        return AI_TYPE_VECTOR;
    else if (attributeDeclaration.find("STRING") != std::string::npos)
        return AI_TYPE_STRING;
    else if (attributeDeclaration.find("NODE") != std::string::npos)
        return AI_TYPE_NODE;
    else if (attributeDeclaration.find("ARRAY") != std::string::npos)
        return AI_TYPE_ARRAY;
    else if (attributeDeclaration.find("MATRIX") != std::string::npos)
        return AI_TYPE_MATRIX;
    else if (attributeDeclaration.find("ENUM") != std::string::npos)
        return AI_TYPE_ENUM;
    else 
        return AI_TYPE_UNDEFINED;
}

void gaExtractUserAttributeViaStringIndex(const std::string& objectName, AtNode* node, StringIndexedStringContainer& subAssetsUserAttributes)
{
	if (subAssetsUserAttributes.find(objectName) == subAssetsUserAttributes.end())
	{
        AiMsgDebug("Failed to find objectName =\"%s\" in subAssetsUserAttributes",objectName.c_str());
		StringIndexedStringContainer::const_iterator siscIter = subAssetsUserAttributes.begin();
		StringIndexedStringContainer::const_iterator siscEIter = subAssetsUserAttributes.end();
		for (;siscIter!=siscEIter;++siscIter)
		{
            AiMsgDebug("sicIter pair<%s,stuff>", siscIter->first.c_str());
		}
		return;
	}
    /* Keep for future debugging purposes
	StringContainer::const_iterator scIter = subAssetsUserAttributes[objectName].begin();
	StringContainer::const_iterator scEIter = subAssetsUserAttributes[objectName].end();
	for (;scIter!=scEIter;++scIter)
	{
		std::cerr << "scIter = " << scIter->c_str() << std::endl;
	}
    */
	const StringContainer& matchingUserAttributeStrings = subAssetsUserAttributes[objectName];
    size_t numCommaSeparatedTokens = matchingUserAttributeStrings.size();
	AiMsgDebug("numCommaSeparatedTokens = %ld",numCommaSeparatedTokens);
	// cstIndex '0' is the matching objectName, user attributes starts from 1
	for (size_t cstIndex = 1;cstIndex < numCommaSeparatedTokens; cstIndex++)
	{
        StringContainer indexedTokenizedStringArray;
    	TokenizeSubAssetAttributes(matchingUserAttributeStrings[cstIndex],";",indexedTokenizedStringArray);
    	bool isArrayAttribute = indexedTokenizedStringArray[1].find("ARRAY") != std::string::npos;
        StringContainer valueTokenizedStringArray;
        if (isArrayAttribute)
            TokenizeSubAssetAttributes(indexedTokenizedStringArray[2],":",valueTokenizedStringArray);
        std::string attributeName = indexedTokenizedStringArray[0];
    	bool declareSuccess = AiNodeDeclare(node,attributeName.c_str(),indexedTokenizedStringArray[1].c_str());
        AtByte attributeType = gaDetermineAttributeType(indexedTokenizedStringArray[1]);
        switch (attributeType)
        {
        case AI_TYPE_BYTE :
            {
            }
            break;
        case AI_TYPE_INT :
            {
                if (isArrayAttribute)
                {
                }
                else
                {
                	AiNodeSetInt(node,attributeName.c_str(),atoi(indexedTokenizedStringArray[2].c_str()));
                }
            }
            break;
        case AI_TYPE_UINT :
            {
            }
            break;
        case AI_TYPE_BOOLEAN :
            {
                if (isArrayAttribute)
                {
                }
                else
                {
                    bool booleanValue = atoi(indexedTokenizedStringArray[2].c_str())!=0;
                    AiNodeSetBool(node,attributeName.c_str(),booleanValue);
                }
            }
            break;
        case AI_TYPE_FLOAT :
            {
                if (isArrayAttribute)
                {
#ifdef MTOA_SUPPORT_ARRAY_USER_ATTRIBUTE_TYPE
                    size_t numElements = valueTokenizedStringArray.size();
                    AtArray* floatArray = AiArrayAllocate(numElements,1, AI_TYPE_FLOAT);
                    for (size_t elementIndex=0;elementIndex<numElements;elementIndex++)
                    {
                        AiArraySetFlt(floatArray,elementIndex,atof(valueTokenizedStringArray[elementIndex].c_str()));
                    }
                    AiNodeSetArray(node, attributeName.c_str(), floatArray);
#endif // MTOA_SUPPORT_ARRAY_USER_ATTRIBUTE_TYPE
                }
                else
                {
                    AiNodeSetFlt(node,attributeName.c_str(),atof(indexedTokenizedStringArray[2].c_str()));
                }
            }
            break;
        case AI_TYPE_RGB :
            {
                if (isArrayAttribute)
                {
                }
                else
                {
                    // Retrieve the color values
                    StringContainer colorTokenizedStringArray;
                    TokenizeSubAssetAttributes(indexedTokenizedStringArray[2],":",colorTokenizedStringArray);
                    if (colorTokenizedStringArray.size()>=3)
                    {
                        float red   = atof(colorTokenizedStringArray[0].c_str());
                        float green = atof(colorTokenizedStringArray[1].c_str());
                        float blue  = atof(colorTokenizedStringArray[2].c_str());
                        AiNodeSetRGB(node,attributeName.c_str(),red,green,blue);
                    }
                }
            }
            break;
        case AI_TYPE_RGBA :
            {
            }
            break;
        case AI_TYPE_VECTOR :
            {
            	std::cerr << "DECLARING VECTOR indexedTokenizedStringArray.size() " << indexedTokenizedStringArray.size() << std::endl;
            	std::cerr << "DECLARING VECTOR indexedTokenizedStringArray[2] " << indexedTokenizedStringArray[2].c_str() << std::endl;
                if (isArrayAttribute)
                {
                }
                else
                {
                    // Retrieve the vector values
                    StringContainer vectorTokenizedStringArray;
                    TokenizeSubAssetAttributes(indexedTokenizedStringArray[2],":",vectorTokenizedStringArray);
                    if (vectorTokenizedStringArray.size()>=3)
                    {
                        float x = atof(vectorTokenizedStringArray[0].c_str());
                        float y = atof(vectorTokenizedStringArray[1].c_str());
                        float z = atof(vectorTokenizedStringArray[2].c_str());
                        AiNodeSetVec(node,attributeName.c_str(),x,y,z);
                    }
                }
            }
            break;
        case AI_TYPE_POINT :
            {
            }
            break;
        case AI_TYPE_POINT2 :
            {
                if (isArrayAttribute)
                {
                }
                else
                {
                    // Retrieve the point2 values
                    StringContainer point2TokenizedStringArray;
                    AiMsgDebug("gaExtractUserAttributeViaStringIndex() POINT2 indexedTokenizedStringArray[2] = %s",
                               indexedTokenizedStringArray[2].c_str());
                    TokenizeSubAssetAttributes(indexedTokenizedStringArray[2],":",point2TokenizedStringArray);
                    if (point2TokenizedStringArray.size()>=2)
                    {

                        AiMsgDebug("gaExtractUserAttributeViaStringIndex() point2TokenizedStringArray[0] = %s",
                                   point2TokenizedStringArray[0].c_str());
                        AiMsgDebug("gaExtractUserAttributeViaStringIndex() point2TokenizedStringArray[1] = %s",
                                   point2TokenizedStringArray[1].c_str());

                        float x = atof(point2TokenizedStringArray[0].c_str());
                        float y = atof(point2TokenizedStringArray[1].c_str());
                        AiNodeSetPnt2(node,attributeName.c_str(),x,y);
                    }
                }
            }
            break;
        case AI_TYPE_STRING :
            {
                if (isArrayAttribute)
                {
                }
                else
                {
                	AiNodeSetStr(node,attributeName.c_str(),indexedTokenizedStringArray[2].c_str());
                }
            }
            break;
        case AI_TYPE_POINTER :
            {
            }
            break;
        case AI_TYPE_NODE :
            {
            }
            break;
        case AI_TYPE_ARRAY :
            {
            }
            break;
        case AI_TYPE_MATRIX :
            {
            }
            break;
        case AI_TYPE_ENUM :
            {
            }
            break;
        default :
            AiMsgError("[gaExtractUserAttributeViaStringIndex] Unknown type encountered");
            break;
        }

	}
}

void gaExtractMtoAPrefixAttributes(Alembic::AbcGeom::ISubDSchema  &isds, AtNode *node)
{
	// Dummy for now due to templatize expansion
}

void gaExtractMtoAPrefixAttributes(Alembic::AbcGeom::IPolyMeshSchema  &ipms, AtNode *node)
{
    Alembic::AbcGeom::ICompoundProperty arbParams = ipms.getArbGeomParams();
    if (arbParams.valid())
    {
        size_t numArbParams = arbParams.getNumProperties();
        for (size_t i=0;i<numArbParams;i++)
        {
        	/*
            std::string propertyTypeName = propertyTypeStringMap[arbParams.getPropertyHeader(i).getPropertyType()];
            printf("ArbParams[%ld] = %s of type [%s]\n",
                   i,
                   arbParams.getPropertyHeader(i).getName().c_str(),
                   propertyTypeName.c_str()
                   );
                   */
            if (arbParams.getPropertyHeader(i).getPropertyType() != Alembic::AbcGeom::kCompoundProperty)
            {
            	/*
                std::string podName = podStringMap[arbParams.getPropertyHeader(i).getDataType().getPod()];
                printf("\tProperty has type %s\n",podName.c_str());
                */
                if (arbParams.getPropertyHeader(i).getName().compare("mtoa_")>0)
                {
                    AiMsgDebug("Found arbgeomparams matching mtoa_* - START\n");
                    StringContainer MtoATokenizedAttribute;
                    TokenizeSubAssetAttributes(arbParams.getPropertyHeader(i).getName(),"_",MtoATokenizedAttribute);
                    {
                        StringContainer::const_iterator iter = MtoATokenizedAttribute.begin();
                        StringContainer::const_iterator eIter = MtoATokenizedAttribute.end();
                        for (;iter!=eIter;++iter)
                        {
                            AiMsgDebug("mtoa_ tokens %s",iter->c_str());
                        }
                    }
                    switch (arbParams.getPropertyHeader(i).getDataType().getPod())
                    {
                    case Alembic::Util::kBooleanPOD :
                        {
                            Alembic::AbcGeom::IBoolGeomParam mtoa_(arbParams,arbParams.getPropertyHeader(i).getName());
                            Alembic::AbcGeom::BoolArraySamplePtr boolArray = mtoa_.getValueProperty().getValue();
                            bool *mtoa_Value = (bool*)(boolArray.get()->getData());
                            AiMsgDebug("\t\tFound mtoa_* value %d\n",*mtoa_Value);
                            AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant BOOL");
                            AiNodeSetBool(node,MtoATokenizedAttribute[2].c_str(),*mtoa_Value);
                        }
                        break;
                    case Alembic::Util::kUint8POD :
                        break;
                    case Alembic::Util::kInt8POD :
                        break;
                    case Alembic::Util::kUint16POD :
                        break;
                    case Alembic::Util::kInt16POD :
                        break;
                    case Alembic::Util::kUint32POD :
                        break;
                    case Alembic::Util::kInt32POD :
                        {
                            Alembic::AbcGeom::IInt32GeomParam mtoa_(arbParams,arbParams.getPropertyHeader(i).getName());
                            Alembic::AbcGeom::Int32ArraySamplePtr intArray = mtoa_.getValueProperty().getValue();
                            int32_t *mtoa_Value = (int32_t*)(intArray.get()->getData());
                            AiMsgDebug("\t\tFound mtoa_* value %d\n",*mtoa_Value);
                            AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant INT");
                            AiNodeSetInt(node,MtoATokenizedAttribute[2].c_str(),*mtoa_Value);
                        }
                        break;
                    case Alembic::Util::kUint64POD :
                        break;
                    case Alembic::Util::kInt64POD :
                        break;
                    case Alembic::Util::kFloat16POD :
                        break;
                    case Alembic::Util::kFloat32POD :
                        {
                            Alembic::AbcGeom::IFloatGeomParam mtoa_(arbParams,arbParams.getPropertyHeader(i).getName());
                            Alembic::AbcGeom::FloatArraySamplePtr floatArray = mtoa_.getValueProperty().getValue();
                            float *mtoa_Value = (float*)(floatArray.get()->getData());
                            std::string interpretation = arbParams.getPropertyHeader(i).getMetaData().get("interpretation");
                            if (interpretation.compare("rgb")==0)
                            {
                                AiMsgDebug("\t\tFound mtoa_* value COLOR[%f,%f,%f]\n",mtoa_Value[0],mtoa_Value[1],mtoa_Value[2]);
                                AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant RGB");
                                AiNodeSetRGB(node,MtoATokenizedAttribute[2].c_str(),mtoa_Value[0],mtoa_Value[1],mtoa_Value[2]);
                            }
                            else if (interpretation.compare("vector")==0)
                            {
                                uint8_t extent = arbParams.getPropertyHeader(i).getDataType().getExtent();
                                /*
                                  printf("\t\tFound mtoa_* value PNT extent = %u\n",extent);
                                  printf("\t\tFound mtoa_* value PNT rank = %lu\n",floatArray.get()->getDimensions().rank());
                                  printf("\t\tFound mtoa_* value PNT numPoints = %lu\n",floatArray.get()->getDimensions().numPoints());
                                  printf("\t\tFound mtoa_* value PNT size = %lu\n",floatArray.get()->size());
                                */
                                switch (extent)
                                {
                                case 2:
                                    AiMsgDebug("\t\tFound mtoa_* value PNT[%f,%f]\n",mtoa_Value[0],mtoa_Value[1]);
                                    AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant POINT2");
                                    AiNodeSetPnt2(node,MtoATokenizedAttribute[2].c_str(),mtoa_Value[0],mtoa_Value[1]);
                                    break;
                                case 3:
                                    AiMsgDebug("\t\tFound mtoa_* value VECTOR[%f,%f,%f]\n",mtoa_Value[0],mtoa_Value[1],mtoa_Value[2]);
                                    AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant VECTOR");
                                    AiNodeSetVec(node,MtoATokenizedAttribute[2].c_str(),mtoa_Value[0],mtoa_Value[1],mtoa_Value[2]);
                                    break;
                                }
                            }
                        }
                        break;
                    case Alembic::Util::kFloat64POD :
                        {
                            Alembic::AbcGeom::IDoubleGeomParam mtoa_(arbParams,arbParams.getPropertyHeader(i).getName());
                            Alembic::AbcGeom::DoubleArraySamplePtr doubleArray = mtoa_.getValueProperty().getValue();
                            double *mtoa_Value = (double*)(doubleArray.get()->getData());
                            std::string interpretation = arbParams.getPropertyHeader(i).getMetaData().get("interpretation");
                            if (interpretation.compare("vector")==0)
                            {
                                AiMsgDebug("\t\tFound mtoa_* value VECTOR[%e,%e,%e]\n",mtoa_Value[0],mtoa_Value[1],mtoa_Value[2]);
                                AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant VECTOR");
                                AiNodeSetVec(node,MtoATokenizedAttribute[2].c_str(),mtoa_Value[0],mtoa_Value[1],mtoa_Value[2]);
                            }
                        }
                        break;
                    case Alembic::Util::kStringPOD :
                        {
                            Alembic::AbcGeom::IStringGeomParam mtoa_(arbParams,arbParams.getPropertyHeader(i).getName());
                            Alembic::AbcGeom::StringArraySamplePtr stringArray = mtoa_.getValueProperty().getValue();
                            char** mtoa_Value = (char **)(stringArray.get()->getData());
                            AiMsgDebug("\t\tFound mtoa_* value STRING [%s]\n",mtoa_Value[0]);
                            AiNodeDeclare(node,MtoATokenizedAttribute[2].c_str(),"constant STRING");
                            AiNodeSetStr(node,MtoATokenizedAttribute[2].c_str(),mtoa_Value[0]);
                        }
                        break;
                    case Alembic::Util::kWstringPOD :
                        break;
                    }
                    AiMsgDebug("Found arbgeomparams matching mtoa_* - END\n");
                }
            }
        }
    }
    
}

void gaAssignSubAssetsAttributes(const ProcArgs& args,AtNode *node)
{
    StringContainer::const_iterator saa_iter = args.aiSubAssetsAttributes.begin();
    StringContainer::const_iterator saa_ei = args.aiSubAssetsAttributes.end();
    bool subAssetIncludeMatched = false;
    for (;saa_iter!=saa_ei && !subAssetIncludeMatched;++saa_iter)
    {
        AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] saa_iter = %s",saa_iter->c_str());
        std::string attributeSeparatorString = ",";
        std::string nodeNameSeparatorString = ";";
        PolyMeshSubAssetAttributes subAssetAttributes;
        bool tokenizeSuccess = TokenizeSubAssetAttributes(*saa_iter,
                                                          attributeSeparatorString,
                                                          nodeNameSeparatorString,
                                                          subAssetAttributes);
        AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] tokenizeSuccess = %s",tokenizeSuccess?"true":"false");
        if (tokenizeSuccess)
        {
            std::string subAssetIncludePattern = subAssetAttributes._includeFilterRegex;
            std::string subAssetExcludePattern = subAssetAttributes._excludeFilterRegex;
            AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] args.aiSubAssetOHeadFullName = [%s]",args.aiSubAssetOHeadFullName.c_str());
            subAssetIncludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetIncludePattern);
            bool subAssetExcludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetExcludePattern);
            AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] subAssetIncludeMatched = %s",subAssetIncludeMatched?"true":"false");
            AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] subAssetExcludeMatched = %s",subAssetExcludeMatched?"true":"false");
            if (subAssetIncludeMatched && !subAssetExcludeMatched)
            {
                AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] calling SetPolyMeshNodeStandardArnoldAttributes");
                // SetPolyMeshNodeStandardArnoldAttributes(meshNode,subAssetAttributes);
                SetPolyMeshNodeStandardArnoldAttributes(node,subAssetAttributes);
            }
            else
            {
                AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] NOT calling SetPolyMeshNodeStandardArnoldAttributes");
            }
        }
    }
}

//-*****************************************************************************

void ApplyTransformation( struct AtNode * node, 
        MatrixSampleMap * xformSamples, ProcArgs &args )
{
    if ( !node || !xformSamples || xformSamples->empty() )
    {
        return;
    }
    
    // confirm that this node has a parameter
    if ( !nodeHasParameter( node, "matrix" ) )
    {
        return;
    }
    
    // check to see that we're not a single identity matrix
    if (xformSamples->size() == 1 &&
            xformSamples->begin()->second == Imath::M44d())
    {
        return;
    }
    
    
    std::vector<float> sampleTimes;
    sampleTimes.reserve(xformSamples->size());
    
    std::vector<float> mlist;
    mlist.reserve( 16* xformSamples->size() );
    
    for ( MatrixSampleMap::iterator I = xformSamples->begin();
            I != xformSamples->end(); ++I )
    {
        // build up a vector of relative sample times to feed to 
        // "transform_time_samples" or "time_samples"
        sampleTimes.push_back( GetRelativeSampleTime(args, (*I).first) );
        
        
        for (int i = 0; i < 16; i++)
        {
            mlist.push_back( (*I).second.getValue()[i] );
        }
    }
    
    AiNodeSetArray(node, "matrix",
                ArrayConvert(1, xformSamples->size(),
                        AI_TYPE_MATRIX, &mlist[0]));
    
    
    if ( sampleTimes.size() > 1 )
    {
#ifdef GRID_ANIMATION_HAS_ANSWER_FROM_SOLID_ANGLE
        // persp_camera calls it time_samples while the primitives call it
        // transform_time_samples
        if ( nodeHasParameter( node, "transform_time_samples" ) )
        {
            AiNodeSetArray(node, "transform_time_samples",
                            ArrayConvert(sampleTimes.size(), 1,
                                    AI_TYPE_FLOAT, &sampleTimes[0]));
        }
        else if ( nodeHasParameter( node, "time_samples" ) )
        {
            AiNodeSetArray(node, "time_samples",
                            ArrayConvert(sampleTimes.size(), 1,
                                    AI_TYPE_FLOAT, &sampleTimes[0]));
        }
        else
        {
            //TODO, warn if neither is present? Should be there in all
            //commercial versions of arnold by now.
        }
#endif // GRID_ANIMATION_HAS_ANSWER_FROM_SOLID_ANGLE
    }
}

//-*****************************************************************************


template <typename geomParamT>
void ProcessIndexedBuiltinParam(
        geomParamT param,
        const SampleTimeSet & sampleTimes,
        std::vector<float> & values,
        std::vector<AtUInt32> & idxs,
        size_t elementSize)
{
    if ( !param.valid() ) { return; }
    
    bool isFirstSample = true;
    for ( SampleTimeSet::iterator I = sampleTimes.begin();
          I != sampleTimes.end(); ++I, isFirstSample = false)
    {
        ISampleSelector sampleSelector( *I );
        
        
        switch ( param.getScope() )
        {
        case kVaryingScope:
        case kVertexScope:
        {
            // a value per-point, idxs should be the same as vidxs
            // so we'll leave it empty
            
            // we'll get the expanded form here
            typename geomParamT::Sample sample = param.getExpandedValue(
                    sampleSelector);
            
            size_t footprint = sample.getVals()->size() * elementSize;
            
            values.reserve( values.size() + footprint );
            values.insert( values.end(),
                    (float32_t*) sample.getVals()->get(),
                    ((float32_t*) sample.getVals()->get()) + footprint );
            
            break;
        }
        case kFacevaryingScope:
        {
            // get the indexed form and feed to nidxs
            
            typename geomParamT::Sample sample = param.getIndexedValue(
                    sampleSelector);
            
            if ( isFirstSample )
            {
                idxs.reserve( sample.getIndices()->size() );
                idxs.insert( idxs.end(),
                        sample.getIndices()->get(),
                        sample.getIndices()->get() +
                                sample.getIndices()->size() );
            }
            
            size_t footprint = sample.getVals()->size() * elementSize;
            values.reserve( values.size() + footprint );
            values.insert( values.end(),
                    (const float32_t*) sample.getVals()->get(),
                    ((const float32_t*) sample.getVals()->get()) + footprint );
            
            break;
        }
        default:
            break;
        }
        
        
    }
    
    
}

//-*****************************************************************************

namespace
{
    // Arnold scene build is single-threaded so we don't have to lock around
    // access to this for now.
    typedef std::map<std::string, AtNode *> NodeCache;
    NodeCache g_meshCache;
}


//-*************************************************************************
// This is templated to handle shared behavior of IPolyMesh and ISubD

// We send in our empty sampleTimes and vidxs because polymesh needs those
// for processing animated normal.


// The return value is the polymesh node. If instanced, it will be returned
// for the first created instance only.
template <typename primT>
AtNode * ProcessPolyMeshBase(
        primT & prim, ProcArgs & args,
        SampleTimeSet & sampleTimes,
        std::vector<AtUInt32> & vidxs,
        int subdiv_iterations,
        MatrixSampleMap * xformSamples, 
        const std::string & facesetName = "" )
{
    if ( !prim.valid() )
    {
        return NULL;
    }
    
	AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] METHOD ENTRY args.aiSubAssetOHeadFullName = [%s]",args.aiSubAssetOHeadFullName.c_str());
    typename primT::schema_type  &ps = prim.getSchema();
    TimeSamplingPtr ts = ps.getTimeSampling();
    
    if ( ps.getTopologyVariance() != kHeterogenousTopology )
    {
        GetRelevantSampleTimes( args, ts, ps.getNumSamples(), sampleTimes );
    }
    else
    {
        sampleTimes.insert( args.frame / args.fps );
    }
    
    std::string name = args.nameprefix + prim.getFullName();
    
    AtNode * instanceNode = NULL;
    
    std::string cacheId;
    
    if ( args.makeInstance )
    {
        std::ostringstream buffer;
        AbcA::ArraySampleKey sampleKey;
        
        
        for ( SampleTimeSet::iterator I = sampleTimes.begin();
                I != sampleTimes.end(); ++I )
        {
            ISampleSelector sampleSelector( *I );
            ps.getPositionsProperty().getKey(sampleKey, sampleSelector);
            
            buffer << GetRelativeSampleTime( args, (*I) ) << ":";
            sampleKey.digest.print(buffer);
            buffer << ":";
        }
        
        buffer << "@" << subdiv_iterations;
        buffer << "@" << facesetName;
        
        cacheId = buffer.str();
        
        instanceNode = AiNode( "ginstance" );
        AiNodeSetStr( instanceNode, "name", name.c_str() );
        args.createdNodes.push_back(instanceNode);
        
        if ( args.proceduralNode )
        {
            AiNodeSetInt( instanceNode, "visibility",
                    AiNodeGetInt( args.proceduralNode, "visibility" ) );
        
        }
        else
        {
            AiNodeSetInt( instanceNode, "visibility", AI_RAY_ALL );
        }
        
        ApplyTransformation( instanceNode, xformSamples, args );
        
        // Grid Animation customization - START
#ifdef VERBOSE_NODE_SUBASSETASSIGNMENT_IMPLEMENTATION
        // TO BE REMOVE
        if (!args.aiSubAssetsAttributes.empty())
        {
            StringContainer::const_iterator saa_iter = args.aiSubAssetsAttributes.begin();
            StringContainer::const_iterator saa_ei = args.aiSubAssetsAttributes.end();
            bool subAssetIncludeMatched = false;
            for (;saa_iter!=saa_ei && !subAssetIncludeMatched;++saa_iter)
            {
                AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] saa_iter = %s",saa_iter->c_str());
                std::string attributeSeparatorString = ",";
                std::string nodeNameSeparatorString = ";";
                PolyMeshSubAssetAttributes subAssetAttributes;
                bool tokenizeSuccess = TokenizeSubAssetAttributes(*saa_iter,
                                                                  attributeSeparatorString,
                                                                  nodeNameSeparatorString,
                                                                  subAssetAttributes);
                AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] tokenizeSuccess = %s",tokenizeSuccess?"true":"false");
                if (tokenizeSuccess)
                {
                    std::string subAssetIncludePattern = subAssetAttributes._includeFilterRegex;
                    std::string subAssetExcludePattern = subAssetAttributes._excludeFilterRegex;
                    AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] args.aiSubAssetOHeadFullName = [%s]",args.aiSubAssetOHeadFullName.c_str());
                    subAssetIncludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetIncludePattern);
                    bool subAssetExcludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetExcludePattern);
                    AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] subAssetIncludeMatched = %s",subAssetIncludeMatched?"true":"false");
                    AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] subAssetExcludeMatched = %s",subAssetExcludeMatched?"true":"false");
                    if (subAssetIncludeMatched && !subAssetExcludeMatched)
                    {
                        AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] calling SetPolyMeshNodeStandardArnoldAttributes");
                        // SetPolyMeshNodeStandardArnoldAttributes(meshNode,subAssetAttributes);
                        SetPolyMeshNodeStandardArnoldAttributes(instanceNode,subAssetAttributes);
                    }
                    else
                    {
                        AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] NOT calling SetPolyMeshNodeStandardArnoldAttributes");
                    }
                }
            }
        }
#else // VERBOSE_NODE_SUBASSETASSIGNMENT_IMPLEMENTATION
        gaExtractMtoAPrefixAttributes(ps,instanceNode);
        gaAssignSubAssetsAttributes(args,instanceNode);
#endif // VERBOSE_NODE_SUBASSETASSIGNMENT_IMPLEMENTATION
        // Grid Animation customization - END
        
        NodeCache::iterator I = g_meshCache.find(cacheId);
        if ( I != g_meshCache.end() )
        {
            AiNodeSetPtr(instanceNode, "node", (*I).second );
            return NULL;
        }
        
    }
    
    
    
    SampleTimeSet singleSampleTimes;
    singleSampleTimes.insert( args.frame / args.fps );
    
    
    std::vector<AtByte> nsides;
    std::vector<float> vlist;
    
    std::vector<float> uvlist;
    std::vector<AtUInt32> uvidxs;
    
    
    // POTENTIAL OPTIMIZATIONS LEFT TO THE READER
    // 1) vlist needn't be copied if it's a single sample
    
    bool isFirstSample = true;
    for ( SampleTimeSet::iterator I = sampleTimes.begin();
          I != sampleTimes.end(); ++I, isFirstSample = false)
    {
        ISampleSelector sampleSelector( *I );
        typename primT::schema_type::Sample sample = ps.getValue( sampleSelector );
        
        if ( isFirstSample )
        {
            size_t numPolys = sample.getFaceCounts()->size();
            nsides.reserve( sample.getFaceCounts()->size() );
            for ( size_t i = 0; i < numPolys; ++i ) 
            {
                Alembic::Util::int32_t n = sample.getFaceCounts()->get()[i];
                
                if ( n > 255 )
                {
                    // TODO, warning about unsupported face
                    return NULL;
                }
                
                nsides.push_back( (AtByte) n );
            }
            
            size_t vidxSize = sample.getFaceIndices()->size();
            vidxs.reserve( vidxSize );
            vidxs.insert( vidxs.end(), sample.getFaceIndices()->get(),
                    sample.getFaceIndices()->get() + vidxSize );
        }
        
        
        vlist.reserve( vlist.size() + sample.getPositions()->size() * 3);
        vlist.insert( vlist.end(),
                (const float32_t*) sample.getPositions()->get(),
                ((const float32_t*) sample.getPositions()->get()) +
                        sample.getPositions()->size() * 3 );
    }
    
    ProcessIndexedBuiltinParam(
            ps.getUVsParam(),
            singleSampleTimes,
            uvlist,
            uvidxs,
            2);
    
    
    AtNode* meshNode = AiNode( "polymesh" );
    
    if (!meshNode)
    {
        AiMsgError("Failed to make polymesh node for %s",
                prim.getFullName().c_str());
        return NULL;
    }
    
    args.createdNodes.push_back(meshNode);
    
    if ( instanceNode != NULL)
    {
        AiNodeSetStr( meshNode, "name", (name + ":src").c_str() );
    }
    else
    {
        AiNodeSetStr( meshNode, "name", name.c_str() );
    }
    
    
    
    
    AiNodeSetArray(meshNode, "vidxs", 
            ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT,
                    (void*)&vidxs[0]));
    
    AiNodeSetArray(meshNode, "nsides",
            ArrayConvert(nsides.size(), 1, AI_TYPE_BYTE,
                    &(nsides[0])));
    
    AiNodeSetArray(meshNode, "vlist",
            ArrayConvert( vlist.size() / sampleTimes.size(), 
                    sampleTimes.size(), AI_TYPE_FLOAT, (void*)(&(vlist[0]))));
    
    if ( !uvlist.empty() )
    {       
        AiNodeSetArray(meshNode, "uvlist",
            ArrayConvert( uvlist.size(), 1, AI_TYPE_FLOAT,
                (void*)(&(uvlist[0]))));
        
        if ( !uvidxs.empty() )
        {
            AiNodeSetArray(meshNode, "uvidxs",
                    ArrayConvert(uvidxs.size(), 1, AI_TYPE_UINT,
                            &(uvidxs[0])));
        }
        else
        {
            AiNodeSetArray(meshNode, "uvidxs",
                    ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT,
                            &(vidxs[0])));
        }
    }
    
    if ( sampleTimes.size() > 1 )
    {
        std::vector<float> relativeSampleTimes;
        relativeSampleTimes.reserve( sampleTimes.size() );
        
        for (SampleTimeSet::const_iterator I = sampleTimes.begin();
                I != sampleTimes.end(); ++I )
        {
            relativeSampleTimes.push_back(
                    GetRelativeSampleTime( args, (*I) ) );
                    
        }
        
#ifdef GRID_ANIMATION_HAS_ANSWER_FROM_SOLID_ANGLE
        AiNodeSetArray( meshNode, "deform_time_samples",
                ArrayConvert(relativeSampleTimes.size(), 1,
                        AI_TYPE_FLOAT, &relativeSampleTimes[0]));
#endif //  GRID_ANIMATION_HAS_ANSWER_FROM_SOLID_ANGLE
    }
    
    // faceset visibility array
    if ( !facesetName.empty() )
    {
        if ( ps.hasFaceSet( facesetName ) )
        {
            ISampleSelector frameSelector( *singleSampleTimes.begin() );
            
            
            IFaceSet faceSet = ps.getFaceSet( facesetName );
            IFaceSetSchema::Sample faceSetSample = 
                    faceSet.getSchema().getValue( frameSelector );
            
            std::set<int> facesToKeep;
            
            
            facesToKeep.insert( faceSetSample.getFaces()->get(),
                    faceSetSample.getFaces()->get() +
                            faceSetSample.getFaces()->size() );
            
            bool *faceVisArray = new bool(nsides.size());
            
            for ( int i = 0; i < (int) nsides.size(); ++i )
            {
                faceVisArray[i] = facesToKeep.find( i ) != facesToKeep.end();
            }
            
            if ( AiNodeDeclare( meshNode, "face_visibility", "uniform BOOL" ) )
            {
                AiNodeSetArray( meshNode, "face_visibility",
                        ArrayConvert( nsides.size(), 1, AI_TYPE_BOOLEAN,
                                faceVisArray ) );
            }
            
            delete[] faceVisArray;
        }
    }
    
    {
        ICompoundProperty arbGeomParams = ps.getArbGeomParams();
        ISampleSelector frameSelector( *singleSampleTimes.begin() );
        
        AddArbitraryGeomParams( arbGeomParams, frameSelector, meshNode );
    }

#ifdef VERBOSE_NODE_SUBASSETASSIGNMENT_IMPLEMENTATION
    // TO BE REMOVE
	if (!args.aiSubAssetsAttributes.empty())
	{
		StringContainer::const_iterator saa_iter = args.aiSubAssetsAttributes.begin();
		StringContainer::const_iterator saa_ei = args.aiSubAssetsAttributes.end();
		bool subAssetIncludeMatched = false;
		for (;saa_iter!=saa_ei && !subAssetIncludeMatched;++saa_iter)
		{
			AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] saa_iter = %s",saa_iter->c_str());
			std::string attributeSeparatorString = ",";
			std::string nodeNameSeparatorString = ";";
			PolyMeshSubAssetAttributes subAssetAttributes;
			bool tokenizeSuccess = TokenizeSubAssetAttributes(*saa_iter,
															  attributeSeparatorString,
															  nodeNameSeparatorString,
															  subAssetAttributes);
			AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] tokenizeSuccess = %s",tokenizeSuccess?"true":"false");
			if (tokenizeSuccess)
			{
				std::string subAssetIncludePattern = subAssetAttributes._includeFilterRegex;
				std::string subAssetExcludePattern = subAssetAttributes._excludeFilterRegex;
				AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] args.aiSubAssetOHeadFullName = [%s]",args.aiSubAssetOHeadFullName.c_str());
				subAssetIncludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetIncludePattern);
				bool subAssetExcludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetExcludePattern);
				AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] subAssetIncludeMatched = %s",subAssetIncludeMatched?"true":"false");
				AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] subAssetExcludeMatched = %s",subAssetExcludeMatched?"true":"false");
				if (subAssetIncludeMatched && !subAssetExcludeMatched)
				{
					AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] calling SetPolyMeshNodeStandardArnoldAttributes");
					SetPolyMeshNodeStandardArnoldAttributes(meshNode,subAssetAttributes);
				}
				else
				{
					AiMsgDebug("[WriteGeo::ProcessPolyMeshBase] NOT calling SetPolyMeshNodeStandardArnoldAttributes");
				}
			}
		}
	}
#else // VERBOSE_NODE_SUBASSETASSIGNMENT_IMPLEMENTATION
        gaExtractMtoAPrefixAttributes(ps,meshNode);
        gaAssignSubAssetsAttributes(args,meshNode);
#endif // VERBOSE_NODE_SUBASSETASSIGNMENT_IMPLEMENTATION

	// Check for Grid Animation custom user attributes
	if (!args.gaSubAssetsUserAttributesStringIndex.empty())
    {
        gaExtractUserAttributeViaStringIndex(name, meshNode, args.gaSubAssetsUserAttributesStringIndex);
    }

    if ( instanceNode == NULL )
    {
        if ( xformSamples )
        {
            ApplyTransformation( meshNode, xformSamples, args );
        }
        
        return meshNode;
    }
    else
    {
        AiNodeSetInt( meshNode, "visibility", 0 );
        
        AiNodeSetPtr(instanceNode, "node", meshNode );
        g_meshCache[cacheId] = meshNode;
        return meshNode;
        
    }
    
}

//-*************************************************************************

void ProcessPolyMesh( IPolyMesh &polymesh, ProcArgs &args,
        MatrixSampleMap * xformSamples, const std::string & facesetName )
{
    SampleTimeSet sampleTimes;
    std::vector<AtUInt32> vidxs;

    // Grid Animation customisation - START
	AiMsgDebug("[WriteGeo::ProcessPolyMesh] METHOD ENTRY args.aiSubAssetOHeadFullName = [%s]",args.aiSubAssetOHeadFullName.c_str());
    bool gaProceed = false;
    args.aiUseDefaultShader = false;
	if (!args.aiSubAssetsAttributes.empty())
	{
		StringContainer::const_iterator saa_iter = args.aiSubAssetsAttributes.begin();
		StringContainer::const_iterator saa_ei = args.aiSubAssetsAttributes.end();
		bool subAssetIncludeMatched = false;
		for (;saa_iter!=saa_ei && !subAssetIncludeMatched;++saa_iter)
		{
			AiMsgDebug("[WriteGeo::ProcessPolyMesh] saa_iter = %s",saa_iter->c_str());
			std::string attributeSeparatorString = ",";
			std::string nodeNameSeparatorString = ";";
			PolyMeshSubAssetAttributes subAssetAttributes;
			bool tokenizeSuccess = TokenizeSubAssetAttributes(*saa_iter,
															  attributeSeparatorString,
															  nodeNameSeparatorString,
															  subAssetAttributes);
			AiMsgDebug("[WriteGeo::ProcessPolyMesh] tokenizeSuccess = %s",tokenizeSuccess?"true":"false");
			if (tokenizeSuccess)
			{
				std::string subAssetIncludePattern = subAssetAttributes._includeFilterRegex;
				std::string subAssetExcludePattern = subAssetAttributes._excludeFilterRegex;
				AiMsgDebug("[WriteGeo::ProcessPolyMesh] args.aiSubAssetOHeadFullName = [%s]",args.aiSubAssetOHeadFullName.c_str());
				subAssetIncludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetIncludePattern);
				bool subAssetExcludeMatched = MatchTextWithPerlRegex(args.aiSubAssetOHeadFullName,subAssetExcludePattern);
				AiMsgDebug("[WriteGeo::ProcessPolyMesh] subAssetIncludeMatched = %s",subAssetIncludeMatched?"true":"false");
				AiMsgDebug("[WriteGeo::ProcessPolyMesh] subAssetExcludeMatched = %s",subAssetExcludeMatched?"true":"false");
				if (subAssetIncludeMatched && !subAssetExcludeMatched)
				{
					gaProceed = true;
				}
				/*
				 * \remark Requirement changes from Grid Animation, they
				 *         now want geometry to be emitted regardless
				 *         but for excluded matches to be shaded with
				 *         Arnold's default shader (grey surface)
				 */
				if (subAssetExcludeMatched)
				{
				    args.aiUseDefaultShader = true;
					gaProceed = true;
				}
			}
		}
	} else
		gaProceed = true;

	if (!gaProceed)
		return;
    // Grid Animation customisation - END

    
    AtNode * meshNode = ProcessPolyMeshBase(
            polymesh, args, sampleTimes, vidxs, 0, xformSamples,
                    facesetName );
    
    // This is a valid condition for the second instance onward and just
    // means that we don't need to do anything further.
    if ( !meshNode )
    {
        return;
    }
    
    IPolyMeshSchema &ps = polymesh.getSchema();
    
    std::vector<float> nlist;
    std::vector<AtUInt32> nidxs;
    
    ProcessIndexedBuiltinParam(
            ps.getNormalsParam(),
            sampleTimes,
            nlist,
            nidxs,
            3);
    
    if ( !nlist.empty() )
    {
        AiNodeSetArray(meshNode, "nlist",
            ArrayConvert( nlist.size() / sampleTimes.size(), 
                    sampleTimes.size(), AI_TYPE_FLOAT, (void*)(&(nlist[0]))));
        
        if ( !nidxs.empty() )
        {
            AiNodeSetArray(meshNode, "nidxs",
                    ArrayConvert(nidxs.size(), 1, AI_TYPE_UINT,
                            &(nidxs[0])));
        }
        else
        {
            AiNodeSetArray(meshNode, "nidxs",
                    ArrayConvert(vidxs.size(), 1, AI_TYPE_UINT,
                            &(vidxs[0])));
        }

    }
    
}

//-*************************************************************************

void ProcessSubD( ISubD &subd, ProcArgs &args,
        MatrixSampleMap * xformSamples, const std::string & facesetName )
{
    SampleTimeSet sampleTimes;
    std::vector<AtUInt32> vidxs;
    
    AtNode * meshNode = ProcessPolyMeshBase(
            subd, args, sampleTimes, vidxs, args.subdIterations,
                    xformSamples, facesetName );
    
    // This is a valid condition for the second instance onward and just
    // means that we don't need to do anything further.
    if ( !meshNode )
    {
        return;
    }
    
    
    AiNodeSetStr( meshNode, "subdiv_type", "catclark" );
}

// For more details about the following two functions, see
// http://stackoverflow.com/questions/3300419/file-name-matching-with-wildcard
void EscapeRegex(string &regex)
{
	AiMsgDebug("[WriteGeo::EscapeRegex] START");
	boost::replace_all(regex, "\\", "\\\\");
	boost::replace_all(regex, "^", "\\^");
	boost::replace_all(regex, ".", "\\.");
	boost::replace_all(regex, "$", "\\$");
	boost::replace_all(regex, "|", "\\|");
	boost::replace_all(regex, "(", "\\(");
	boost::replace_all(regex, ")", "\\)");
	boost::replace_all(regex, "[", "\\[");
	boost::replace_all(regex, "]", "\\]");
	boost::replace_all(regex, "*", "\\*");
	boost::replace_all(regex, "+", "\\+");
	boost::replace_all(regex, "?", "\\?");
	boost::replace_all(regex, "/", "\\/");
	AiMsgDebug("[WriteGeo::EscapeRegex] END");
}

void EscapePerlRegex(string &regex)
{
	AiMsgDebug("[WriteGeo::EscapePerlRegex] START");
	// boost::replace_all(regex, "\\", "\\\\");
	// boost::replace_all(regex, "^", "\\^");
	// boost::replace_all(regex, ".", "\\.");
	// boost::replace_all(regex, "$", "\\$");
	// boost::replace_all(regex, "|", "\\|");
	// boost::replace_all(regex, "(", "\\(");
	// boost::replace_all(regex, ")", "\\)");
	// boost::replace_all(regex, "[", "\\[");
	// boost::replace_all(regex, "]", "\\]");
	boost::replace_all(regex, "*", "\\*");
	// boost::replace_all(regex, "+", "\\+");
	boost::replace_all(regex, "?", "\\?");
	// boost::replace_all(regex, "/", "\\/");
	AiMsgDebug("[WriteGeo::EscapePerlRegex] END");
}

bool MatchTextWithWildcards(const string &text, string wildcardPattern, bool caseSensitive)
{
	AiMsgDebug("[WriteGeo::MatchTextWithWildcards] START");
	// Escape all regex special chars
	EscapeRegex(wildcardPattern);

	// Convert chars '*?' back to their regex equivalents
	boost::replace_all(wildcardPattern, "\\?", ".");
	boost::replace_all(wildcardPattern, "\\*", ".*");

	wildcardPattern = "^" + wildcardPattern + "$";

	boost::regex pattern(wildcardPattern, caseSensitive ? boost::regex::normal : boost::regex::icase);

	AiMsgDebug("[WriteGeo::MatchTextWithWildcards] wildcardPattern = \"%s\"",wildcardPattern.c_str());
	AiMsgDebug("[WriteGeo::MatchTextWithWildcards] text = \"%s\"",text.c_str());
	AiMsgDebug("[WriteGeo::MatchTextWithWildcards] END");
	return regex_match(text, pattern);
}

bool MatchTextWithPerlRegex(const string &text, string wildcardPattern)
{
	boost::regex pattern(wildcardPattern);

	bool matched = regex_match(text, pattern);

	AiMsgDebug("[WriteGeo::MatchTextWithPerlRegex] START wildcardPattern = \"%s\"",wildcardPattern.c_str());
	AiMsgDebug("[WriteGeo::MatchTextWithPerlRegex] text = \"%s\"",text.c_str());
	AiMsgDebug("[WriteGeo::MatchTextWithPerlRegex] matched = \"%s\"",matched?"True":"False");
	AiMsgDebug("[WriteGeo::MatchTextWithPerlRegex] END");
	return matched;
}

void TokenizeSubAssetAttributes(const std::string &attributesString, const std::string& separatorString, StringContainer& tokens)
{
    boost::char_separator<char> sep(separatorString.c_str(),"",boost::keep_empty_tokens);
    boost::tokenizer< boost::char_separator<char> > attribute_tokens(attributesString, sep);
    tokens.clear();
    BOOST_FOREACH (const std::string& t, attribute_tokens) {
        tokens.push_back(t);
		AiMsgDebug("[WriteGeo::TokenizeSubAssetAttributes] token = %s",t.c_str());
    }
}

void ProcessTokenAsBoolean(const std::string &tokenString, bool& attribute)
{
	if (!tokenString.empty())
	{
		attribute = tokenString.compare(trueString) == 0;
	}
}
void ProcessTokenAsUnsignedShort(const std::string &tokenString, unsigned short& attribute)
{
	if (!tokenString.empty())
		attribute = atoi(tokenString.c_str());
}
void ProcessTokenAsInteger(const std::string &tokenString, int& attribute)
{
	if (!tokenString.empty())
		attribute = atoi(tokenString.c_str());
}
void ProcessTokenAsFloat(const std::string &tokenString, float& attribute)
{
	if (!tokenString.empty())
		attribute = atof(tokenString.c_str());
}
void ProcessTokenAsFloats(const std::string &tokenString, const std::string& separatorString, FloatContainer attribute)
{
	if (!tokenString.empty())
	{
		boost::char_separator<char> sep(separatorString.c_str(),"",boost::keep_empty_tokens);
		boost::tokenizer< boost::char_separator<char> > attribute_tokens(tokenString, sep);
		attribute.clear();
		BOOST_FOREACH (const std::string& t, attribute_tokens) {
			attribute.push_back(atof(t.c_str()));
		}
	}
}
void ProcessTokenAsStringContainer(const std::string &tokenString, const std::string& separatorString, StringContainer& attribute)
{
	if (!tokenString.empty())
	{
		boost::char_separator<char> sep(separatorString.c_str(),"",boost::keep_empty_tokens);
		boost::tokenizer< boost::char_separator<char> > attribute_tokens(tokenString, sep);
		attribute.clear();
		BOOST_FOREACH (const std::string& t, attribute_tokens) {
			attribute.push_back(t);
		}
	}
}

bool TokenizeSubAssetAttributes(const std::string &attributesString,
								const std::string& attributeSeparatorString,
								const std::string& nodeNameSeparatorString,
								PolyMeshSubAssetAttributes& subAssetAttributes)
{
	StringContainer attributeTokens;
	boost::char_separator<char> sep(attributeSeparatorString.c_str(),"",boost::keep_empty_tokens);
	boost::tokenizer< boost::char_separator<char> > attributeTokenizer(attributesString, sep);
	attributeTokens.clear();
	BOOST_FOREACH (const std::string& t, attributeTokenizer)
	{
		attributeTokens.push_back(t);
		AiMsgDebug("[WriteGeo::TokenizeSubAssetAttributes] token = %s",t.c_str());
	}
	if (attributeTokens.size() != PolyMeshSubAssetAttributes::_attributeCount)
	{
		AiMsgDebug("[WriteGeo::TokenizeSubAssetAttributes] attributesString[\"%s\"] tokens count of %ld is not of expected value %ld",
				attributesString.c_str(),attributeTokens.size(),PolyMeshSubAssetAttributes::_attributeCount);
		return false;
	}
	/*
	  With the required information, process them
	*/

	const std::string nodeSeparatorString(";");

	// Processing _includeFilterRegex
	subAssetAttributes._includeFilterRegex = attributeTokens[0];
	// Processing _excludeFilterRegex
	subAssetAttributes._excludeFilterRegex = attributeTokens[1];
	// Processing _shader
	ProcessTokenAsStringContainer(attributeTokens[2],nodeSeparatorString,subAssetAttributes._shader);
	// Processing _opaque
	ProcessTokenAsBoolean(attributeTokens[3],subAssetAttributes._opaque);
	// Processing _smoothing
	ProcessTokenAsBoolean(attributeTokens[4],subAssetAttributes._smoothing);
	// Processing _subdiv_type
	ProcessTokenAsUnsignedShort(attributeTokens[5],subAssetAttributes._subdiv_type);
	// Processing _subdiv_iterations
	ProcessTokenAsUnsignedShort(attributeTokens[6],subAssetAttributes._subdiv_iterations);
	// Processing _subdiv_pixel_error
	ProcessTokenAsFloat(attributeTokens[7],subAssetAttributes._subdiv_pixel_error);
	// Processing _subdiv_dicing_camera
	subAssetAttributes._subdiv_dicing_camera = attributeTokens[8];
	// Processing _subdiv_adaptive_metric
	ProcessTokenAsUnsignedShort(attributeTokens[9],subAssetAttributes._subdiv_adaptive_metric);
	// Processing _subdiv_uv_smoothing
	ProcessTokenAsUnsignedShort(attributeTokens[10],subAssetAttributes._subdiv_uv_smoothing);
	// Processing _subdiv_smooth_derivs
	ProcessTokenAsBoolean(attributeTokens[11],subAssetAttributes._subdiv_smooth_derivs);
	// Processing _disp_padding
	ProcessTokenAsFloat(attributeTokens[12],subAssetAttributes._disp_padding);
	// Processing _disp_height
	ProcessTokenAsFloat(attributeTokens[13],subAssetAttributes._disp_height);
	// Processing _disp_zero_value
	ProcessTokenAsFloat(attributeTokens[14],subAssetAttributes._disp_zero_value);
	// Processing _disp_autobump
	ProcessTokenAsBoolean(attributeTokens[15],subAssetAttributes._disp_autobump);
	// Processing _use_light_group
	ProcessTokenAsBoolean(attributeTokens[16],subAssetAttributes._use_light_group);
	// Processing _light_group
	ProcessTokenAsStringContainer(attributeTokens[17],nodeSeparatorString,subAssetAttributes._light_group);
	// Processing _use_shadow_group
	ProcessTokenAsBoolean(attributeTokens[18],subAssetAttributes._use_shadow_group);
	// Processing _shadow_group
	ProcessTokenAsStringContainer(attributeTokens[19],nodeSeparatorString,subAssetAttributes._shadow_group);
	// Processing _visibility
	ProcessTokenAsInteger(attributeTokens[20],subAssetAttributes._visibility);
	// Processing _id
	ProcessTokenAsInteger(attributeTokens[21],subAssetAttributes._id);
	// Processing _sidedness
	ProcessTokenAsInteger(attributeTokens[22],subAssetAttributes._sidedness);
	// Processing _receive_shadows
	ProcessTokenAsBoolean(attributeTokens[23],subAssetAttributes._receive_shadows);
	// Processing _self_shadows
	ProcessTokenAsBoolean(attributeTokens[24],subAssetAttributes._self_shadows);
	// Processing _invert_normals
	ProcessTokenAsBoolean(attributeTokens[25],subAssetAttributes._invert_normals);
	// Processing _ray_bias
	ProcessTokenAsFloat(attributeTokens[26],subAssetAttributes._ray_bias);
	// Processing _trace_sets
	ProcessTokenAsStringContainer(attributeTokens[27],nodeSeparatorString,subAssetAttributes._trace_sets);
	// Processing _transform_time_samples[2
	ProcessTokenAsFloats(attributeTokens[28], nodeSeparatorString, subAssetAttributes._transform_time_samples);
	// Processing _deform_time_samples[2
	ProcessTokenAsFloats(attributeTokens[29], nodeSeparatorString, subAssetAttributes._deform_time_samples);
	// Processing _sss_sample_spacing
	ProcessTokenAsFloat(attributeTokens[30],subAssetAttributes._sss_sample_spacing);
	// Processing _sss_sample_distribution
	ProcessTokenAsUnsignedShort(attributeTokens[31],subAssetAttributes._sss_sample_distribution);

	return true;
}

/*!
 * Data to process :
 * 	 StringContainer 	_shader;
 * 	 bool            	_opaque;
 * 	 bool				_smoothing;
 * 	 unsigned short		_subdiv_type;
 * 	 unsigned short		_subdiv_iterations;
 * 	 float				_subdiv_pixel_error;
 * 	 std::string		_subdiv_dicing_camera;
 * 	 unsigned short		_subdiv_adaptive_metric;
 * 	 unsigned short		_subdiv_uv_smoothing;
 * 	 bool				_subdiv_smooth_derivs;
 * 	 float				_disp_padding;
 * 	 float				_disp_height;
 * 	 float				_disp_zero_value;
 * 	 bool				_disp_autobump;
 * 	 bool				_use_light_group;
 * 	 StringContainer	_light_group;
 * 	 bool				_use_shadow_group;
 * 	 StringContainer	_shadow_group;
 * 	 int				_visibility;
 * 	 int				_id;
 * 	 int				_sidedness;
 * 	 bool				_receive_shadows;
 * 	 bool				_self_shadows;
 * 	 bool				_invert_normals;
 * 	 float				_ray_bias;
 * 	 StringContainer	_trace_sets;
 * 	 FloatContainer		_transform_time_samples;
 * 	 FloatContainer		_deform_time_samples;
 * 	 float				_sss_sample_spacing;
 * 	 unsigned short		_sss_sample_distribution;
 *
 */
void SetPolyMeshNodeStandardArnoldAttributes(AtNode *meshNode,const PolyMeshSubAssetAttributes& subAssetAttributes)
{
	// The set up code is written in the same order as the data layout more for maintenance ease

	// * 	 StringContainer 	_shader;
	if (!subAssetAttributes._shader.empty())
	{
		std::string shadingGroupString = subAssetAttributes._shader[0];
		if (!shadingGroupString.empty())
		{
			AtNode *shader = AiNodeLookUpByName(shadingGroupString.c_str());
			if (shader)
			{
				AiMsgDebug("[WriteGeo::SetPolyMeshNodeStandardArnoldAttributes] shader found [%s]",shadingGroupString.c_str());
				AiNodeSetPtr( meshNode, "shader", shader );

				// For this to work within the MtoA viewport rendering in Maya, the following is required
				if (AiNodeDeclare( meshNode, "mtoa_shading_groups", "constant ARRAY NODE" ))
				{
					std::vector<AtNode*> meshShaders;
					meshShaders.push_back(shader);
					AiNodeSetArray(meshNode, "mtoa_shading_groups",
								   AiArrayConvert(1, 1, AI_TYPE_NODE, &(meshShaders[0])));
				}
			}
		}
	}

	// * 	 bool            	_opaque;
	AiNodeSetBool( meshNode, "opaque", subAssetAttributes._opaque );
	
	// * 	 bool				_smoothing;
	AiNodeSetBool( meshNode, "smoothing", subAssetAttributes._smoothing );

	// * 	 unsigned short		_subdiv_type;
	AiMsgDebug("[WriteGeo::SetPolyMeshNodeStandardArnoldAttributes] subAssetAttributes._subdiv_type [%d]",subAssetAttributes._subdiv_type);
	AiNodeSetInt( meshNode, "subdiv_type", subAssetAttributes._subdiv_type );

	// * 	 unsigned short		_subdiv_iterations;
	AiNodeSetInt( meshNode, "subdiv_iterations", subAssetAttributes._subdiv_iterations );

	// * 	 float				_subdiv_pixel_error;
	AiNodeSetFlt( meshNode, "subdiv_pixel_error", subAssetAttributes._subdiv_pixel_error );

	// * 	 std::string		_subdiv_dicing_camera;
	if (!subAssetAttributes._subdiv_dicing_camera.empty())
	{
		AtNode *dicing_camera = AiNodeLookUpByName(subAssetAttributes._subdiv_dicing_camera.c_str());
		if (dicing_camera)
			AiNodeSetPtr ( meshNode, "subdiv_dicing_camera", dicing_camera );
	}

	// * 	 unsigned short		_subdiv_adaptive_metric;
	AiNodeSetInt( meshNode, "subdiv_adaptive_metric", subAssetAttributes._subdiv_adaptive_metric );

	// * 	 unsigned short		_subdiv_uv_smoothing;
	AiNodeSetInt( meshNode, "subdiv_uv_smoothing", subAssetAttributes._subdiv_uv_smoothing );

	// * 	 bool				_subdiv_smooth_derivs;
	AiNodeSetBool( meshNode, "subdiv_smooth_derivs", subAssetAttributes._subdiv_smooth_derivs );

	// * 	 float				_disp_padding;
	AiNodeSetFlt( meshNode, "disp_padding", subAssetAttributes._disp_padding );

	// * 	 float				_disp_height;
	AiNodeSetFlt( meshNode, "disp_height", subAssetAttributes._disp_height );

	// * 	 float				_disp_zero_value;
	AiNodeSetFlt( meshNode, "disp_zero_value", subAssetAttributes._disp_zero_value );

	// * 	 bool				_disp_autobump;
	AiNodeSetBool( meshNode, "disp_autobump", subAssetAttributes._disp_autobump );

	// * 	 bool				_use_light_group;
	AiNodeSetBool( meshNode, "use_light_group", subAssetAttributes._use_light_group );

	// * 	 StringContainer	_light_group;
	if (!subAssetAttributes._light_group.empty())
	{
		StringContainer::const_iterator llt_iter = subAssetAttributes._light_group.begin();
		StringContainer::const_iterator llt_ei = subAssetAttributes._light_group.end();
		std::vector<AtNode*> meshLights;
		for (;llt_iter!=llt_ei;++llt_iter)
		{
			AtNode *light = AiNodeLookUpByName(llt_iter->c_str());
			if (light)
			{
				meshLights.push_back(light);
				AiMsgDebug("[WriteGeo::SetPolyMeshNodeStandardArnoldAttributes] adding lights \"%s\"",llt_iter->c_str());
			}
		}
		if (meshLights.size()>0)
		{
			AiNodeSetArray(meshNode, "light_group",
						   AiArrayConvert(meshLights.size(), 1, AI_TYPE_NODE, &(meshLights[0])));
		}
	} else {
		AiNodeSetBool(meshNode, "use_light_group", false);
	}

	// * 	 bool				_use_shadow_group;
	AiNodeSetBool( meshNode, "use_shadow_group", subAssetAttributes._use_shadow_group );

	// * 	 StringContainer	_shadow_group;
	if (!subAssetAttributes._shadow_group.empty())
	{
		StringContainer::const_iterator shg_iter = subAssetAttributes._shadow_group.begin();
		StringContainer::const_iterator sg_ei = subAssetAttributes._shadow_group.end();
		std::vector<AtNode*> meshShadows;
		for (;shg_iter!=sg_ei;++shg_iter)
		{
			AtNode *shadow = AiNodeLookUpByName(shg_iter->c_str());
			if (shadow)
			{
				meshShadows.push_back(shadow);
				AiMsgDebug("[WriteGeo::SetPolyMeshNodeStandardArnoldAttributes] adding shadows \"%s\"",shg_iter->c_str());
			}
		}
		if (meshShadows.size()>0)
		{
			AiNodeSetArray(meshNode, "shadow_group",
						   AiArrayConvert(meshShadows.size(), 1, AI_TYPE_NODE, &(meshShadows[0])));
		}
	} else {
		AiNodeSetBool(meshNode, "use_shadow_group", false);
	}

	// * 	 int				_visibility;
	AiNodeSetInt( meshNode, "visibility", subAssetAttributes._visibility );

	// * 	 int				_id;
	AiNodeSetInt( meshNode, "id", subAssetAttributes._id );

	// * 	 int				_sidedness;
	AiNodeSetInt( meshNode, "sidedness", subAssetAttributes._sidedness );

	// * 	 bool				_receive_shadows;
	AiNodeSetBool( meshNode, "receive_shadows", subAssetAttributes._receive_shadows );

	// * 	 bool				_self_shadows;
	AiNodeSetBool( meshNode, "self_shadows", subAssetAttributes._self_shadows );

	// * 	 bool				_invert_normals;
	AiNodeSetBool( meshNode, "invert_normals", subAssetAttributes._invert_normals );

	// * 	 float				_ray_bias;
	AiNodeSetFlt( meshNode, "ray_bias", subAssetAttributes._ray_bias );

	// * 	 StringContainer	_trace_sets;
	if (!subAssetAttributes._trace_sets.empty())
	{
		const size_t numSets = subAssetAttributes._trace_sets.size();
		AtArray* array = AiArrayAllocate(numSets, 1, AI_TYPE_STRING);
		for (size_t i = 0; i < numSets; ++i)
            AiArraySetStr(array, i, subAssetAttributes._trace_sets[i].c_str());
		AiNodeSetArray(meshNode, "trace_sets", array);
	}

	// * 	 FloatContainer		_transform_time_samples;
	if (subAssetAttributes._transform_time_samples.size()==2)
	{
		AiNodeSetArray(meshNode, "transform_time_samples",
					   AiArrayConvert(subAssetAttributes._transform_time_samples.size(), 1,
									  AI_TYPE_FLOAT, &subAssetAttributes._transform_time_samples[0]));
	}

	// * 	 FloatContainer		_deform_time_samples;
	if (subAssetAttributes._deform_time_samples.size()==2)
	{
		AiNodeSetArray(meshNode, "deform_time_samples",
					   AiArrayConvert(subAssetAttributes._deform_time_samples.size(), 1,
									  AI_TYPE_FLOAT, &subAssetAttributes._deform_time_samples[0]));
	}

	// * 	 float				_sss_sample_spacing;
	AiNodeSetFlt( meshNode, "sss_sample_spacing", subAssetAttributes._sss_sample_spacing );

	// * 	 unsigned short		_sss_sample_distribution;
	AiNodeSetInt( meshNode, "sss_sample_distribution", subAssetAttributes._sss_sample_distribution );

}
